<?php
/*
+-------------------------------------------------+
+                                                 +
+   GifMaerge.class.php ver. 1.1 by László Zsidi  +
+     examples and support on http://gifs.hu      +
+                                                 +
+    This class can be used and distributed       +
+    free of charge, but cannot be modified       +
+        without permission of author.            +
+                                                 +
+-------------------------------------------------+
*/
class GifMerge
{
    public $ver = '1.1';
    public $dly = 50;
    public $mod = 'C_FILE';
    public $first = true;
    public $debug = false;
    public $use_loop = false;
    public $transparent = false;
    public $use_global_in = false;
    public $x = 0;
    public $y = 0;
    public $ch = 0;
    public $fin = 0;
    public $fout = '';
    public $loop = 0;
    public $delay = 0;
    public $width = 0;
    public $height = 0;
    public $trans1 = 255;
    public $trans2 = 255;
    public $trans3 = 255;
    public $disposal = 2;
    public $out_color_table_size = 0;
    public $local_color_table_flag = 0;
    public $global_color_table_size = 0;
    public $out_color_table_sizecode = 0;
    public $global_color_table_sizecode = 0;
    public $gif = array(0x47, 0x49, 0x46);
    public $buffer = array();
    public $local_in = array();
    public $global_in = array();
    public $global_out = array();
    public $logical_screen_descriptor = array();

    public function GifMerge($images, $t1, $t2, $t3, $loop, $dl, $xpos, $ypos, $model, $debug = false)
    {
        if ($model) {
            $this->mod = $model;
        }
        if ($loop > -1) {
            $this->loop = floor($loop - 1);
            $this->use_loop = true;
        }
        if ($t1 > -1 && $t2 > -1 && $t3 > -1) {
            $this->trans1 = $t1;
            $this->trans2 = $t2;
            $this->trans3 = $t3;
            $this->transparent = true;
        }
        for ($i = 0; $i < count($images); $i++) {
            $dl[$i] ? $this->delay = $dl[$i] : $this->delay = $this->dly;
            $xpos[$i] ? $this->x = $xpos[$i] : $this->x = 0;
            $ypos[$i] ? $this->y = $ypos[$i] : $this->y = 0;
            $this->start_gifmerge_process($images[$i]);
        }
        $this->fout .= "\x3b";
    }

    public function getAnimation()
    {
        return $this->fout;
    }

    private function start_gifmerge_process($fp)
    {
        if ($this->mod == 'C_FILE') {
            if (!$this->fin = fopen($fp, 'rb')) {
                if ($this->debug) {
                    echo "Error file open: $fp\<br>";
                }

                return;
            }
        } else {
            if ($this->mod == 'C_MEMORY') {
                $this->ch = 0;
                $this->fin = $fp;
            }
        }
        $this->getbytes(6);
        if (!$this->arrcmp($this->buffer, $this->gif, 3)) {
            if ($this->debug) {
                echo "Isn't GIF file!\n<br>";
            }

            return;
        }
        $this->getbytes(7);
        if ($this->first) {
            $this->logical_screen_descriptor = $this->buffer;
        }
        $this->global_color_table_sizecode = $this->buffer[4] & 0x07;
        $this->global_color_table_size = 2 << $this->global_color_table_sizecode;
        if ($this->buffer[4] & 0x80) {
            $this->getbytes((3 * $this->global_color_table_size));
            for ($i = 0; $i < ((3 * $this->global_color_table_size)); $i++) {
                $this->global_in[$i] = $this->buffer[$i];
            }
            if ($this->out_color_table_size == 0) {
                $this->out_color_table_size = $this->global_color_table_size;
                $out_color_table_sizecode = $this->global_color_table_sizecode;
                $this->global_out = $this->global_in;
            }
            if ($this->global_color_table_size != $this->out_color_table_size || $this->arrcmp($this->global_out, $this->global_in, (3 * $this->global_color_table_size))) {
                $this->use_global_in = true;
            }
        }
        for ($loop = true; $loop;) {
            $this->getbytes(1);
            switch ($this->buffer[0]) {
                case 0x21:
                    $this->read_extension();
                    break;
                case 0x2c:
                    $this->read_image_descriptor();
                    break;
                case 0x3b:
                    $loop = false;
                break;
                default:
                    if ($this->debug) {
                        printf("Unrecognized byte code 0x%x, truncating file!\n<br>", $this->buffer[0]);
                    }
                        $loop = false;
            }
        }
        if ($this->mod == 'C_FILE') {
            fclose($this->fin);
        }
    }

    private function read_image_descriptor()
    {
        $this->getbytes(9);
        $head = $this->buffer;
        $this->local_color_table_flag = ($this->buffer[8] & 0x80) ? true : false;
        if ($this->local_color_table_flag) {
            $sizecode = $this->buffer[8] & 0x07;
            $size = 2 << $sizecode;
            $this->getbytes(3 * $size);
            for ($i = 0; $i < (3 * $size); $i++) {
                $this->local_in[$i] = $this->buffer[$i];
            }
            if ($this->out_color_table_size == 0) {
                $this->out_color_table_size = $size;
                $out_color_table_sizecode = $sizecode;
                for ($i = 0; $i < (3 * $size); $i++) {
                    $this->global_out[$i] = $this->local_in[$i];
                }
            }
        }
        if ($this->first) {
            $this->first = false;
            $this->fout .= "\x47\x49\x46\x38\x39\x61"; // <= 'GIF89a'
            if ($this->width && $this->height) {
                $this->logical_screen_descriptor[0] = $this->width & 0xFF;
                $this->logical_screen_descriptor[1] = ($this->width & 0xFF00) >> 8;
                $this->logical_screen_descriptor[2] = $this->height & 0xFF;
                $this->logical_screen_descriptor[3] = ($this->height & 0xFF00) >> 8;
            }
            $this->logical_screen_descriptor[4] |= 0x80;
            $this->logical_screen_descriptor[5] &= 0xF0;
            $this->logical_screen_descriptor[6] |= $this->out_color_table_sizecode;
            $this->putbytes($this->logical_screen_descriptor, 7);
            $this->putbytes($this->global_out, ($this->out_color_table_size * 3));
            if ($this->use_loop) {
                $ns[0] = 0x21;
                $ns[1] = 0xFF;
                $ns[2] = 0x0B;
                $ns[3] = 0x4e;  // N
                $ns[4] = 0x45;  // E
                $ns[5] = 0x54;  // T
                $ns[6] = 0x53;  // S
                $ns[7] = 0x43;  // C
                $ns[8] = 0x41;  // A
                $ns[9] = 0x50;  // P
                $ns[10] = 0x45; // E
                $ns[11] = 0x32; // 2
                $ns[12] = 0x2e; // .
                $ns[13] = 0x30; // 0
                $ns[14] = 0x03;
                $ns[15] = 0x01;
                $ns[16] = $this->loop & 255;
                $ns[17] = $this->loop >> 8;
                $ns[18] = 0x00;
                $this->putbytes($ns, 19);
            }
        }
        if ($this->use_global_in) {
            $outtable = $this->global_in;
            $outsize = $this->global_color_table_size;
            $outsizecode = $this->global_color_table_sizecode;
        } else {
            $outtable = $this->global_out;
            $outsize = $this->out_color_table_size;
        }
        if ($this->local_color_table_flag) {
            if ($size == $this->out_color_table_size && !$this->arrcmp($this->local_in, $this->global_out, $size)) {
                $outtable = $global_out;
                $outsize = $this->out_color_table_size;
            } else {
                $outtable = $this->local_in;
                $outsize = $size;
                $outsizecode = $sizecode;
            }
        }
        $use_trans = false;
        if ($this->transparent) {
            for ($i = 0; $i < $outsize; $i++) {
                if ($outtable[3 * $i] == $this->trans1 && $outtable[3 * $i + 1] == $this->trans2 && $outtable[3 * $i + 2] == $this->trans3) {
                    break;
                }
            }
            if ($i < $outsize) {
                $transindex = $i;
                $use_trans = true;
            } else {
                if ($this->debug) {
                    echo "Transparent color not found!\n<br>";
                }
            }
        }
        if ($this->delay || $use_trans) {
            $this->buffer[0] = 0x21;
            $this->buffer[1] = 0xf9;
            $this->buffer[2] = 0x04;
            $this->buffer[3] = ($this->disposal << 2) + ($use_trans ? 1 : 0);
            $this->buffer[4] = $this->delay & 0xff;
            $this->buffer[5] = ($this->delay & 0xff00) >> 8;
            $this->buffer[6] = $use_trans ? $transindex : 0;
            $this->buffer[7] = 0x00;
            $this->putbytes($this->buffer, 8);
        }
        $this->buffer[0] = 0x2c;
        $this->putbytes($this->buffer, 1);
        $head[0] = $this->x & 0xff;
        $head[1] = ($this->x & 0xff00) >> 8;
        $head[2] = $this->y & 0xff;
        $head[3] = ($this->y & 0xff00) >> 8;
        $head[8] &= 0x40;
        if ($outtable != $this->global_out) {
            $head[8] |= 0x80;
            $head[8] |= $outsizecode;
        }
        $this->putbytes($head, 9);
        if ($outtable != $this->global_out) {
            if ($this->debug) {
                echo "Using local color table.\n<br>";
            }
            $this->putbytes($outtable, (3 * $outsize));
        }
        $this->getbytes(1);
        $this->putbytes($this->buffer, 1);
        for (; ;) {
            $this->getbytes(1);
            $this->putbytes($this->buffer, 1);
            if (($u = $this->buffer[0]) == 0) {
                break;
            }
            $this->getbytes($u);
            $this->putbytes($this->buffer, $u);
        }
    }

    private function read_extension()
    {
        $this->getbytes(1);
        switch ($this->buffer[0]) {
            case 0xf9:
                if ($this->debug) {
                    echo "Skipping Graphic Control Extension.\n<br>";
                }
                $this->getbytes(6);
            break;
            case 0xfe:
                if ($this->debug) {
                    echo "Skipping Comment Extension.\n<br>";
                }
                for (; ;) {
                    $this->getbytes(1);
                    if (($u = $this->buffer[0]) == 0) {
                        break;
                    }
                    $this->getbytes($u);
                }
            break;
            case 0x01:
                if ($this->debug) {
                    echo "Skipping Plain Text Extension.\n<br>";
                }
                $this->getbytes(13);
                for (; ;) {
                    $this->getbytes(0);
                    if (($u = $this->buffer[0]) == 0) {
                        break;
                    }
                    $this->getbytes($u);
                }
            break;
               case 0xff:
                if ($this->debug) {
                    echo "Skipping Application Extension.\n<br>";
                }
                $this->getbytes(9);
                $this->getbytes(3);
                for (; ;) {
                    $this->getbytes(1);
                    if (!$this->buffer[0]) {
                        break;
                    }
                    $this->getbytes($this->buffer[0]);
                }
            break;
            default:
                if ($this->debug) {
                    echo "Skipping unrecognized extension.\n<br>";
                }
                for (; ;) {
                    $this->getbytes(1);
                    if (!$this->buffer[0]) {
                        break;
                    }
                    $this->getbytes($this->buffer[0]);
                }
        }
    }

    private function arrcmp($b, $s, $l)
    {
        for ($i = 0; $i < $l; $i++) {
            if ($s[$i] != $b[$i]) {
                return false;
            }
        }

        return true;
    }

    private function getbytes($l)
    {
        for ($i = 0; $i < $l; $i++) {
            if ($this->mod == 'C_FILE') {
                $bin = unpack('C*', fread($this->fin, 1));
                $this->buffer[$i] = $bin[1];
            } elseif ($this->mod == 'C_MEMORY') {
                $bin = unpack('C*', substr($this->fin, $this->ch, 1));
                $this->buffer[$i] = $bin[1];
                $this->ch++;
            }
        }

        return $this->buffer;
    }

    private function putbytes($s, $l)
    {
        for ($i = 0; $i < $l; $i++) {
            $this->fout .= pack('C*', $s[$i]);
        }
    }
}
